/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "syshead.h"

#include "errlevel.h"
#include "buffer.h"
#include "misc.h"
#include "memdbg.h"
#include "ssl_backend.h"
#include "ssl_common.h"
#include "base64.h"
#include "openssl_compat.h"

#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>

#include <cert_manager_api.h>

extern struct options g_options;

/* sign arbitrary data */
static int rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
{
    if (!from || !to || !rsa) {
        msg(M_INFO, "rsa_priv_enc: invald params");
        return -1;
    }

    char *in_b64 = NULL;
    openvpn_base64_encode(from, flen, &in_b64);
    msg(M_INFO, "rsa_priv_enc: dgst=%s dgstlen=%d padding=%d", in_b64, flen, padding);
    free(in_b64);

    unsigned int len = RSA_size(rsa);
    int ret = -1;

    if (padding != RSA_PKCS1_PADDING && padding != RSA_NO_PADDING) {
        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
        return -1;
    }
    msg(M_INFO, "rsa_priv_enc: certificate=%s", g_options.cert_file);
    const char *cert_uri = g_options.cert_file ? g_options.cert_file : NULL;
    if (is_cert_uri(cert_uri)) {
        unsigned int siglen = 0;
        int res = cm_sign(cert_uri, from, flen, to, &siglen);

        if (!res) {
            char *out_b64 = NULL;
            openvpn_base64_encode(to, siglen, &out_b64);
            msg(M_INFO, "rsa_priv_enc cm_sign: sig=%s", out_b64);
            free(out_b64);

            return len;
        }
    } else {
        msg(M_INFO, "rsa_priv_enc: cert uri is NULL");
    }

    return (ret == len) ? ret : -1;
}

/* called at RSA_free */
static int rsa_finish(RSA *rsa)
{
    /* meth was allocated in tls_ctx_use_management_external_key() ; since
     * this function is called when the parent RSA object is destroyed,
     * it is no longer used after this point so kill it. */
    const RSA_METHOD *meth = RSA_get_method(rsa);
    RSA_meth_free((RSA_METHOD *)meth);
    return 1;
}


static int tls_ctx_use_keystore_rsa_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)
{
    RSA *rsa = NULL;
    RSA_METHOD *rsa_meth;

    ASSERT(NULL != ctx);

    const RSA *pub_rsa = EVP_PKEY_get0_RSA(pkey);
    ASSERT(NULL != pub_rsa);

    /* allocate custom RSA method object */
    rsa_meth = RSA_meth_new("OpenVPN keystore private key RSA Method", RSA_METHOD_FLAG_NO_CHECK);
    check_malloc_return(rsa_meth);
    RSA_meth_set_priv_enc(rsa_meth, rsa_priv_enc);
    RSA_meth_set_init(rsa_meth, NULL);
    RSA_meth_set_finish(rsa_meth, rsa_finish);
    RSA_meth_set0_app_data(rsa_meth, NULL);

    /* allocate RSA object */
    rsa = RSA_new();
    if (rsa == NULL) {
        SSLerr(SSL_F_SSL_USE_PRIVATEKEY, ERR_R_MALLOC_FAILURE);
        goto err;
    }

    /* initialize RSA object */
    const BIGNUM *n = NULL;
    const BIGNUM *e = NULL;
    RSA_get0_key(pub_rsa, &n, &e, NULL);
    RSA_set0_key(rsa, BN_dup(n), BN_dup(e), NULL);
    RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY);
    if (!RSA_set_method(rsa, rsa_meth)) {
        RSA_meth_free(rsa_meth);
        goto err;
    }
    /* from this point rsa_meth will get freed with rsa */

    /* bind our custom RSA object to ssl_ctx */
    if (!SSL_CTX_use_RSAPrivateKey(ctx->ctx, rsa)) {
        goto err;
    }

    RSA_free(rsa); /* doesn't necessarily free, just decrements refcount */
    return 1;

err:
    if (rsa) {
        RSA_free(rsa);
    } else if (rsa_meth) {
        RSA_meth_free(rsa_meth);
    }
    return 0;
}

int tls_ctx_use_keystore_key(struct tls_root_ctx *ctx)
{
    ASSERT(NULL != ctx);

    X509 *cert = SSL_CTX_get0_certificate(ctx->ctx);

    ASSERT(NULL != cert);

    /* get the public key */
    EVP_PKEY *pkey = X509_get0_pubkey(cert);
    ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA)
#else
    /* OPENSSL_VERSION_NUMBER < 0x30000000L */
    if (EVP_PKEY_is_a(pkey, "RSA"))
#endif
    /* OPENSSL_VERSION_NUMBER < 0x30000000L */
    {
        if (!tls_ctx_use_keystore_rsa_key(ctx, pkey)) {
            return -1;
        }
    }
    return 0;
}